﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Common.Utilities.Dispatcher;
using System.Threading;
using System.Data.SqlClient;
using System.IO;

namespace Workflows.Components.TextMining.EnumerationObjects
{
    /// <summary>
    /// 1. get list of docs from local drive
    /// 2. read forward from db for any docs that do not exist in local drive
    /// 3. download doc content to local drive one-by-one
    /// </summary> 
    public class PdfDocListWorker:IWorker
    {
        #region fields
        private Thread _WorkerThread;
        private PdfDocListInstruction _Instruction;
        private PdfDocListOutput _Output;
        private RunContext _Context;
        private Guid _JobTicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }
        #endregion

        #region IWorker Members

        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        public ResultBase Output
        {
            get { return _Output; }
        }

        public Type ComponentType
        {
            get { return typeof(PdfDocListComponent); }
        }

        public RunContext Context
        {
            get
            {
                return _Context;
            }
            set
            {
                _Context=value;
            }
        }

        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (PdfDocListInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._Context = context;
            this._JobTicketID = ticketGuid;
            this._Instruction = (PdfDocListInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }

            this._WorkerThread = Thread.CurrentThread;
            this.ProvidePdfDocList();
        }

        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }

            this._WorkerThread = new Thread(new ThreadStart(this.ProvidePdfDocList));
            this._WorkerThread.Start();
        }

        public void CancelWork()
        {
            if (this._WorkerThread != null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._JobTicketID, this.GetType(), this._Instruction);
                }
            }
        }

        public event WorkerStarted WorkerStartedEvent;

        public event WorkerFinished WorkerFinishedEvent;

        public event WorkerHasError WorkerErredEvent;

        public event WorkerCancelled WorkerCancelledEvent;

        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #endregion

        #region work
        private void ProvidePdfDocList()
        {
            try
            {
                Dictionary<string, int> filePathIDMappings =
                    this.GetPdfDocs(
                        this._Instruction.ConnStr,
                        this._Instruction.SelectSqlCommand,
                        this._Instruction.FileNameField,
                        this._Instruction.DocIDField,
                        this._Instruction.BFileFieldName,
                        this._Instruction.FileNameFormat,
                        this._Instruction.LocalFolderPath,
                        this._Instruction.FileFilter,
                        this._Instruction.IncludeSubfolders,
                        this._Instruction.Compensation);

                XmlTextWriter outputWriter =new XmlTextWriter(this._Instruction.ResultFilePath,Encoding.UTF8);
                outputWriter.WriteStartElement("Files");
                int fileCount = 0;
                if (filePathIDMappings != null && filePathIDMappings.Count > 0)
                {
                    fileCount = filePathIDMappings.Count;
                    foreach (string filePath in filePathIDMappings.Keys)
                    {
                        int docID = filePathIDMappings[filePath];
                        outputWriter.WriteStartElement("File");
                        outputWriter.WriteAttributeString("DocID", docID.ToString());
                        outputWriter.WriteAttributeString("FilePath",filePath);
                        outputWriter.WriteEndElement();
                    }
                }
                outputWriter.WriteEndElement();
                outputWriter.Flush();
                outputWriter.Close();

                this._Output=new PdfDocListOutput(this._Instruction.OwnerComponentID,this._JobTicketID,
                    this._Instruction.ResultFilePath, fileCount);

                if(this.WorkerFinishedEvent !=null)
                {
                    this.WorkerFinishedEvent(this._JobTicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                {
                    this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction, ex.ToString(),true);
                }
            }
        }
        #endregion

        #region retrieve docs
        /// <summary>
        /// requires name to be unique (not by path)
        /// </summary>
        /// <param name="connStr"></param>
        /// <param name="selectSql"></param>
        /// <param name="fileNameField"></param>
        /// <param name="docIDField"></param>
        /// <param name="bfileField"></param>
        /// <param name="fileNameFormat"></param>
        /// <param name="localFolderPath"></param>
        /// <param name="fileFilter"></param>
        /// <param name="includeSubfolder"></param>
        /// <param name="compensationMethod"></param>
        /// <returns></returns>
        private Dictionary<string, int> GetPdfDocs(string connStr, string selectSql, 
            string fileNameField, string docIDField, string bfileField,
            string fileNameFormat, string localFolderPath, 
            string fileFilter, bool includeSubfolder, 
            PdfDocListInstruction.DocListCompensationMethod compensationMethod)
        {
            Dictionary<string, int> filePathIDMappings = new Dictionary<string, int>();
            Dictionary<string, string> fileNamePathMappings = new Dictionary<string, string>();

            // first look through local folder 
            if (Directory.Exists(localFolderPath))
            {
                DirectoryInfo rootDirInfo = new DirectoryInfo(localFolderPath);
                FileInfo[] pdfFileInfos = null;
                if (includeSubfolder)
                {
                    pdfFileInfos = rootDirInfo.GetFiles(fileFilter, SearchOption.AllDirectories);
                }
                else
                {
                    pdfFileInfos = rootDirInfo.GetFiles(fileFilter, SearchOption.TopDirectoryOnly);
                }

                foreach (FileInfo pdfFileInfo in pdfFileInfos)
                {
                    if (!fileNamePathMappings.ContainsKey(pdfFileInfo.Name.ToUpper()))
                    {
                        fileNamePathMappings.Add(pdfFileInfo.Name.ToUpper(), pdfFileInfo.FullName);
                    }
                }
            }

            // then look through db
            if (!string.IsNullOrEmpty(connStr) && !string.IsNullOrEmpty(selectSql))
            {
                SqlConnection conn = new SqlConnection(connStr);

                try
                {
                    conn.Open();
                    SqlCommand cmd = conn.CreateCommand();
                    string selectMetaDataSql =
                        string.Format("select distinct {0},{1} from ({2}) tmp",
                            docIDField,
                            fileNameField,
                            selectSql);
                    cmd.CommandText = selectMetaDataSql;
                    SqlDataReader reader = cmd.ExecuteReader();
                    int totalFileCount = fileNamePathMappings.Count;
                    int retrievedFileCount = 0;
                    List<int> docsNeedsToDownloadFromDB = new List<int>();
                    while (reader.Read())
                    {
                        int docID = int.Parse(reader[docIDField].ToString());
                        string fileName = reader[fileNameField].ToString();
                        if (!string.IsNullOrEmpty(fileNameFormat) && fileNameFormat.Contains("{0}"))
                        {
                            fileName = string.Format(fileNameFormat, fileName);
                        }
                        if (!fileNamePathMappings.ContainsKey(fileName.ToUpper()))
                        {
                            docsNeedsToDownloadFromDB.Add(docID);
                        }
                        else
                        {
                            string filePath = fileNamePathMappings[fileName.ToUpper()];
                            filePathIDMappings.Add(filePath, docID);
                            
                            retrievedFileCount++;
                        }
                        totalFileCount++;
                        if (this.WorkerStatusChangedEvent != null && retrievedFileCount%10 == 0)
                        {
                            int percent = retrievedFileCount*100/totalFileCount;
                            string msg = string.Format("Retrived {0} of {1} docs", retrievedFileCount, totalFileCount);
                            this.WorkerStatusChangedEvent(this._JobTicketID, this.GetType(), this._Instruction, this.WorkflowName, this.ComponentName, msg,
                                                          percent);
                        }
                    }
                    reader.Close();

                    if (docsNeedsToDownloadFromDB.Count > 0 && 
                        (compensationMethod==PdfDocListInstruction.DocListCompensationMethod.DbLocalFileTwoWayCompensate || 
                        compensationMethod==PdfDocListInstruction.DocListCompensationMethod.DbToLocalFile))
                    {
                        int fileDownloaded = 0;
                        foreach (int docID in docsNeedsToDownloadFromDB)
                        {
                            string selectBFileDataSql =
                                string.Format("select {0},{1},{2} from ({3}) tmp where {0}={4}",
                                    docIDField,
                                    fileNameField,
                                    bfileField,
                                    selectSql,
                                    docID);
                            cmd.CommandText = selectBFileDataSql;
                            reader = cmd.ExecuteReader();
                            if (reader.Read())
                            {
                                string fileName = reader[fileNameField].ToString();
                                if (reader[bfileField] != null && reader[bfileField] != DBNull.Value)
                                {
                                    try
                                    {
                                        byte[] fileContent = (byte[])reader[bfileField];
                                        if (fileContent != null && fileContent.Length > 0)
                                        {
                                            string filePath = this.SaveFile(fileContent, localFolderPath, fileName);
                                            filePathIDMappings.Add(filePath, docID);
                                        }
                                        fileDownloaded++;
                                    }
                                    catch (Exception ex3)
                                    {
                                        if (this.WorkerErredEvent != null)
                                        {
                                            this.WorkerErredEvent(
                                                this._JobTicketID, this.GetType(),
                                                this._Instruction,
                                                "Failed to download " + fileName +
                                                " from db: " + ex3.ToString(),false);
                                        }
                                    }
                                }
                            }
                            reader.Close();

                            if (this.WorkerStatusChangedEvent != null && fileDownloaded % 10 == 0)
                            {
                                int percent = fileDownloaded * 100 / docsNeedsToDownloadFromDB.Count;
                                string msg = string.Format("Downloaded {0} of {1} docs", fileDownloaded, docsNeedsToDownloadFromDB.Count);
                                this.WorkerStatusChangedEvent(this._JobTicketID, this.GetType(), this._Instruction, this.WorkflowName, this.ComponentName, msg,
                                                              percent);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    if (this.WorkerErredEvent != null)
                    {
                        this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction, ex.ToString(),true);
                    }
                }
            }
            else
            {
                int docID = 0;
                foreach(string fileName in fileNamePathMappings.Keys)
                {
                    string filePath = fileNamePathMappings[fileName];
                    docID++;
                    filePathIDMappings.Add(filePath, docID);
                }
            }
            return filePathIDMappings;
        }

        private string SaveFile(byte[] contents, string folderPath, string fileName)
        {
            string tgtFilePath = Path.Combine(folderPath, fileName);
            if (!File.Exists(tgtFilePath))
            {
                BinaryWriter writer = new BinaryWriter(File.OpenWrite(tgtFilePath));
                writer.Write(contents, 0, contents.Length);
                writer.Flush();
                writer.Close();
            }
            return tgtFilePath;
        }
        #endregion
    }
}
